/*
 * Copyright (c) 2015-2018, Armink, <armink.ztl@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: Environment variables operating interface. (wear leveling mode)
 * Created on: 2015-02-11
 */

#include <easyflash.h>
#include <string.h>
#include <stdlib.h>

#ifdef EF_USING_ENV

#ifdef EF_ENV_USING_WL_MODE

/**
 * ENV area has 2 sections
 * 1. System section
 *    Storage ENV current using data section address.
 *    Units: Word. Total size: @see EF_ERASE_MIN_SIZE.
 * 2. Data section
 *    The data section storage ENV's parameters and detail.
 *    When an exception has occurred on flash erase or write. The current using data section
 *    address will move to next available position. This position depends on EF_ERASE_MIN_SIZE.
 *    2.1 ENV parameters part
 *        It storage ENV's parameters.
 *    2.2 ENV detail part
 *        It storage all ENV. Storage format is key=value\0.
 *        All ENV must be 4 bytes alignment. The remaining part must fill '\0'.
 *
 * @note Word = 4 Bytes in this file
 * @note It will has two ENV areas(Area0, Area1) in data section when used power fail safeguard mode.
 */

/* flash ENV parameters part index and size */
enum {
	/* data section ENV detail part end address index */
	ENV_PARAM_PART_INDEX_END_ADDR = 0,

#ifdef EF_ENV_USING_PFS_MODE
	/* saved count for ENV area */
	ENV_PARAM_PART_INDEX_SAVED_COUNT,
#endif

	/* data section CRC32 code index */
	ENV_PARAM_PART_INDEX_DATA_CRC,
	/* ENV parameters part word size */
	ENV_PARAM_PART_WORD_SIZE,
	/* ENV parameters part byte size */
	ENV_PARAM_PART_BYTE_SIZE = ENV_PARAM_PART_WORD_SIZE * 4,
};

/* default ENV set, must be initialized by user */
static ef_env const *default_env_set;
/* default ENV set size, must be initialized by user */
static size_t default_env_set_size = 0;
/* flash ENV data section size */
static size_t env_data_section_size = 0;
/* ENV ram cache */
static uint32_t env_cache[ENV_USER_SETTING_SIZE / 4] = { 0 };
/* ENV start address in flash */
static uint32_t env_start_addr = 0;
/* current using data section address */
static uint32_t cur_using_data_addr = 0;
/* ENV ram cache has changed when ENV created, deleted and changed value. */
static bool env_cache_changed = false;
/* initialize OK flag */
static bool init_ok = false;

#ifdef EF_ENV_USING_PFS_MODE
/* next save ENV area address */
static uint32_t next_save_area_addr = 0;
#endif

static uint32_t get_env_start_addr(void);
static uint32_t get_cur_using_data_addr(void);
static uint32_t get_env_detail_addr(void);
static uint32_t get_env_detail_end_addr(void);
static void set_cur_using_data_addr(uint32_t using_data_addr);
static void set_env_detail_end_addr(uint32_t end_addr);
static EfErrCode write_env(const char *key, const char *value);
static char *find_env(const char *key);
static size_t get_env_detail_size(void);
static size_t get_env_user_used_size(void);
static EfErrCode create_env(const char *key, const char *value);
static EfErrCode del_env(const char *key);
static EfErrCode save_cur_using_data_addr(uint32_t cur_data_addr);
static uint32_t calc_env_crc(void);
static bool env_crc_is_ok(void);

/**
 * Flash ENV initialize.
 *
 * @param default_env default ENV set for user
 * @param default_env_size default ENV set size
 *
 * @return result
 */
EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size)
{
	EfErrCode result = EF_NO_ERR;

	EF_ASSERT(ENV_AREA_SIZE);
	EF_ASSERT(ENV_USER_SETTING_SIZE);
	/* must be word alignment for ENV */
	EF_ASSERT(ENV_USER_SETTING_SIZE % 4 == 0);
	EF_ASSERT(ENV_AREA_SIZE % 4 == 0);
	EF_ASSERT(default_env);
	EF_ASSERT(default_env_size < ENV_USER_SETTING_SIZE);

#ifndef EF_ENV_USING_PFS_MODE
	/* system section size is erase_min_size, so last part is data section */
	env_data_section_size = ENV_AREA_SIZE - EF_ERASE_MIN_SIZE;
#else
	/* system section size is erase_min_size, so last part is data section */
	env_data_section_size = ENV_AREA_SIZE / 2 - EF_ERASE_MIN_SIZE;
	EF_ASSERT((ENV_AREA_SIZE / EF_ERASE_MIN_SIZE) % 2 == 0);
#endif
	EF_ASSERT(env_data_section_size >= ENV_USER_SETTING_SIZE);
	/* the ENV data section size should be an integral multiple of erase minimum size. */
	EF_ASSERT(env_data_section_size % EF_ERASE_MIN_SIZE == 0);


	env_start_addr = EF_START_ADDR;
	default_env_set = default_env;
	default_env_set_size = default_env_size;

	EF_DEBUG("ENV start address is 0x%08X, size is %d bytes.\n", EF_START_ADDR, ENV_AREA_SIZE);

	result = ef_load_env();

	if (result == EF_NO_ERR)
		init_ok = true;

	return result;
}

/**
 * ENV set default.
 *
 * @return result
 */
EfErrCode ef_env_set_default(void)
{
	EfErrCode result = EF_NO_ERR;
	size_t i;

	EF_ASSERT(default_env_set);
	EF_ASSERT(default_env_set_size);

	/* lock the ENV cache */
	ef_port_env_lock();

	/* set ENV detail part end address is at ENV detail part start address */
	set_env_detail_end_addr(get_env_detail_addr());

#ifdef EF_ENV_USING_PFS_MODE
	/* set saved count to default 0 */
	env_cache[ENV_PARAM_PART_INDEX_SAVED_COUNT] = 0;
#endif

	/* create default ENV */
	for (i = 0; i < default_env_set_size; i++)
		create_env(default_env_set[i].key, default_env_set[i].value);

	/* unlock the ENV cache */
	ef_port_env_unlock();

	result = ef_save_env();

#ifdef EF_ENV_USING_PFS_MODE
	/* reset other PFS area's data */
	if (result == EF_NO_ERR) {
		env_cache_changed = true;
		result = ef_save_env();
	}
#endif

	return result;
}

/**
 * Get ENV start address in flash.
 *
 * @return ENV start address in flash
 */
static uint32_t get_env_start_addr(void)
{
	return env_start_addr;
}
/**
 * Get current using data section address.
 *
 * @return current using data section address
 */
static uint32_t get_cur_using_data_addr(void)
{
	return cur_using_data_addr;
}

/**
 * Set current using data section address.
 *
 * @param using_data_addr current using data section address
 */
static void set_cur_using_data_addr(uint32_t using_data_addr)
{
	cur_using_data_addr = using_data_addr;
}

/**
 * Get ENV detail part start address.
 *
 * @return detail part start address
 */
static uint32_t get_env_detail_addr(void)
{
	return get_cur_using_data_addr() + ENV_PARAM_PART_BYTE_SIZE;
}

/**
 * Get ENV detail part end address.
 * It's the first word in ENV.
 *
 * @return ENV end address
 */
static uint32_t get_env_detail_end_addr(void)
{
	/* it is the first word */
	return env_cache[ENV_PARAM_PART_INDEX_END_ADDR];
}

/**
 * Set ENV detail part end address.
 * It's the first word in ENV.
 *
 * @param end_addr ENV end address
 */
static void set_env_detail_end_addr(uint32_t end_addr)
{
	env_cache[ENV_PARAM_PART_INDEX_END_ADDR] = end_addr;
}

/**
 * Get current ENV detail part size.
 *
 * @return size
 */
static size_t get_env_detail_size(void)
{
	if (get_env_detail_end_addr() > get_env_detail_addr())
		return get_env_detail_end_addr() - get_env_detail_addr();
	else
		return 0;
}

/**
 * Get current user used ENV size.
 *
 * @see ENV_USER_SETTING_SIZE
 *
 * @return size
 */
/* must be initialized */
static size_t get_env_user_used_size(void)
{
	if (get_env_detail_end_addr() > get_cur_using_data_addr())
		return get_env_detail_end_addr() - get_cur_using_data_addr();
	else
		return 0;
}

/**
 * Get current ENV already write bytes.
 *
 * @return write bytes
 */
size_t ef_get_env_write_bytes(void)
{
#ifndef EF_ENV_USING_PFS_MODE
	return get_env_detail_end_addr() - get_env_start_addr();
#else
	return EF_ERASE_MIN_SIZE + get_env_detail_end_addr() - get_cur_using_data_addr();
#endif
}

/**
 * Write an ENV at the end of cache.
 *
 * @param key ENV name
 * @param value ENV value
 *
 * @return result
 */
static EfErrCode write_env(const char *key, const char *value)
{
	EfErrCode result = EF_NO_ERR;
	size_t ker_len = strlen(key), value_len = strlen(value), env_str_len;
	char *env_cache_bak = (char *)env_cache;

	/* calculate ENV storage length, contain '=' and '\0'. */
	env_str_len = ker_len + value_len + 2;
	if (env_str_len % 4 != 0)
		env_str_len = (env_str_len / 4 + 1) * 4;
	/* check capacity of ENV  */
	if (env_str_len + get_env_user_used_size() >= ENV_USER_SETTING_SIZE)
		return EF_ENV_FULL;
	/* calculate current ENV ram cache end address */
	env_cache_bak += ENV_PARAM_PART_BYTE_SIZE + get_env_detail_size();
	/* copy key name */
	memcpy(env_cache_bak, key, ker_len);
	env_cache_bak += ker_len;
	/* copy equal sign */
	*env_cache_bak = '=';
	env_cache_bak++;
	/* copy value */
	memcpy(env_cache_bak, value, value_len);
	env_cache_bak += value_len;
	/* fill '\0' for string end sign */
	*env_cache_bak = '\0';
	env_cache_bak ++;
	/* fill '\0' for word alignment */
	memset(env_cache_bak, 0, env_str_len - (ker_len + value_len + 2));
	set_env_detail_end_addr(get_env_detail_end_addr() + env_str_len);
	/* ENV ram cache has changed */
	env_cache_changed = true;

	return result;
}

/**
 * Find ENV.
 *
 * @param key ENV name
 *
 * @return found ENV in ram cache
 */
static char *find_env(const char *key)
{
	char *env_start, *env_end, *env, *found_env = NULL;
	size_t key_len = strlen(key), env_len;

	if (*key == NULL) {
		EF_INFO("Flash ENV name must be not empty!\n");
		return NULL;
	}

	/* from data section start to data section end */
	env_start = (char *)((char *) env_cache + ENV_PARAM_PART_BYTE_SIZE);
	env_end = (char *)((char *) env_cache + ENV_PARAM_PART_BYTE_SIZE + get_env_detail_size());

	/* ENV is null */
	if (env_start == env_end)
		return NULL;

	env = env_start;
	while (env < env_end) {
		/* the key length must be equal */
		if (!strncmp(env, key, key_len) && (env[key_len] == '=')) {
			found_env = env;
			break;
		} else {
			/* calculate ENV length, contain '\0'. */
			env_len = strlen(env) + 1;
			/* next ENV and word alignment */
			if (env_len % 4 == 0)
				env += env_len;
			else
				env += (env_len / 4 + 1) * 4;
		}
	}

	return found_env;
}

/**
 * If the ENV is not exist, create it.
 * @see flash_write_env
 *
 * @param key ENV name
 * @param value ENV value
 *
 * @return result
 */
static EfErrCode create_env(const char *key, const char *value)
{
	EfErrCode result = EF_NO_ERR;

	EF_ASSERT(key);
	EF_ASSERT(value);

	if (*key == NULL) {
		EF_INFO("Flash ENV name must be not empty!\n");
		return EF_ENV_NAME_ERR;
	}

	if (strchr(key, '=')) {
		EF_INFO("Flash ENV name can't contain '='.\n");
		return EF_ENV_NAME_ERR;
	}

	/* find ENV */
	if (find_env(key)) {
		EF_INFO("The name of \"%s\" is already exist.\n", key);
		return EF_ENV_NAME_EXIST;
	}
	/* write ENV at the end of cache */
	result = write_env(key, value);

	return result;
}

/**
 * Delete an ENV in cache.
 *
 * @param key ENV name
 *
 * @return result
 */
static EfErrCode del_env(const char *key)
{
	EfErrCode result = EF_NO_ERR;
	char *del_env = NULL;
	size_t del_env_length, remain_env_length;

	EF_ASSERT(key);

	if (*key == NULL) {
		EF_INFO("Flash ENV name must be not NULL!\n");
		return EF_ENV_NAME_ERR;
	}

	if (strchr(key, '=')) {
		EF_INFO("Flash ENV name or value can't contain '='.\n");
		return EF_ENV_NAME_ERR;
	}

	/* find ENV */
	del_env = find_env(key);

	if (!del_env) {
		EF_INFO("Not find \"%s\" in ENV.\n", key);
		return EF_ENV_NAME_ERR;
	}
	del_env_length = strlen(del_env);
	/* '\0' also must be as ENV length */
	del_env_length ++;
	/* the address must multiple of 4 */
	if (del_env_length % 4 != 0)
		del_env_length = (del_env_length / 4 + 1) * 4;
	/* calculate remain ENV length */
	remain_env_length = get_env_detail_size()
						- (((uint32_t) del_env + del_env_length) - ((uint32_t) env_cache + ENV_PARAM_PART_BYTE_SIZE));
	/* remain ENV move forward */
	memcpy(del_env, del_env + del_env_length, remain_env_length);
	/* reset ENV end address */
	set_env_detail_end_addr(get_env_detail_end_addr() - del_env_length);
	/* ENV ram cache has changed */
	env_cache_changed = true;

	return result;
}

/**
 * Set an ENV. If it value is empty, delete it.
 * If not find it in ENV table, then create it.
 *
 * @param key ENV name
 * @param value ENV value
 *
 * @return result
 */
EfErrCode ef_set_env(const char *key, const char *value)
{
	EfErrCode result = EF_NO_ERR;
	char *old_env, *old_value;

	if (!init_ok) {
		EF_INFO("ENV isn't initialize OK.\n");
		return EF_ENV_INIT_FAILED;
	}

	/* lock the ENV cache */
	ef_port_env_lock();

	/* if ENV value is empty, delete it */
	if (*value == NULL)
		result = del_env(key);
	else {
		old_env = find_env(key);
		/* If find this ENV, then compare the new value and old value. */
		if (old_env) {
			/* find the old value address */
			old_env = strchr(old_env, '=');
			old_value = old_env + 1;
			/* If it is changed then delete it and recreate it  */
			if (strcmp(old_value, value)) {
				result = del_env(key);
				if (result == EF_NO_ERR)
					result = create_env(key, value);
			}
		} else
			result = create_env(key, value);
	}
	/* unlock the ENV cache */
	ef_port_env_unlock();

	return result;
}

/**
 * Get an ENV value by key name.
 *
 * @param key ENV name
 *
 * @return value
 */
char *ef_get_env(const char *key)
{
	char *env = NULL, *value = NULL;

	if (!init_ok) {
		EF_INFO("ENV isn't initialize OK.\n");
		return NULL;
	}

	/* find ENV */
	env = find_env(key);

	if (env == NULL)
		return NULL;
	/* get value address */
	value = strchr(env, '=');
	if (value != NULL) {
		/* the equal sign next character is value */
		value++;
	}
	return value;
}
/**
 * Print ENV.
 */
void ef_print_env(void)
{
	uint32_t *env_cache_detail_addr = env_cache + ENV_PARAM_PART_WORD_SIZE, *env_cache_end_addr =
										  (uint32_t *)(env_cache + ENV_PARAM_PART_WORD_SIZE + get_env_detail_size() / 4);
	uint8_t j;
	char c;

	if (!init_ok) {
		EF_INFO("ENV isn't initialize OK.\n");
		return;
	}

	for (; env_cache_detail_addr < env_cache_end_addr; env_cache_detail_addr += 1) {
		for (j = 0; j < 4; j++) {
			c = (*env_cache_detail_addr) >> (8 * j);
			ef_print("%c", c);
			if (c == NULL) {
				ef_print("\n");
				break;
			}
		}
	}

#ifndef EF_ENV_USING_PFS_MODE
	ef_print("\nENV size: %ld/%ld bytes, write bytes %ld/%ld, mode: wear leveling.\n",
			 get_env_user_used_size(), ENV_USER_SETTING_SIZE, ef_get_env_write_bytes(),
			 ENV_AREA_SIZE);
#else
	ef_print("\nENV size: %ld/%ld bytes, write bytes %ld/%ld, saved count: %ld, mode: wear leveling and power fail safeguard.\n",
			 get_env_user_used_size(), ENV_USER_SETTING_SIZE, ef_get_env_write_bytes(),
			 ENV_AREA_SIZE / 2, env_cache[ENV_PARAM_PART_INDEX_SAVED_COUNT]);
#endif
}

/**
 * Load flash ENV to ram.
 *
 * @return result
 */
#ifndef EF_ENV_USING_PFS_MODE
EfErrCode ef_load_env(void)
{
	EfErrCode result = EF_NO_ERR;
	uint32_t *env_cache_bak, env_end_addr, using_data_addr;

	/* read current using data section address */
	ef_port_read(get_env_start_addr(), &using_data_addr, 4);
	/* if ENV is not initialize or flash has dirty data, set default for it */
	if ((using_data_addr == 0xFFFFFFFF)
		|| (using_data_addr > get_env_start_addr() + ENV_AREA_SIZE)
		|| (using_data_addr < get_env_start_addr() + EF_ERASE_MIN_SIZE)) {
		/* initialize current using data section address */
		set_cur_using_data_addr(get_env_start_addr() + EF_ERASE_MIN_SIZE);
		/* save current using data section address to flash*/
		if ((result = save_cur_using_data_addr(get_cur_using_data_addr())) == EF_NO_ERR) {
			/* set default ENV */
			result = ef_env_set_default();
		}
	} else {
		/* set current using data section address */
		set_cur_using_data_addr(using_data_addr);
		/* read ENV detail part end address from flash */
		ef_port_read(get_cur_using_data_addr() + ENV_PARAM_PART_INDEX_END_ADDR * 4, &env_end_addr, 4);
		/* if ENV end address has error, set default for ENV */
		if (env_end_addr > get_env_start_addr() + ENV_AREA_SIZE) {
			/* initialize current using data section address */
			set_cur_using_data_addr(get_env_start_addr() + EF_ERASE_MIN_SIZE);
			/* save current using data section address to flash*/
			if ((result = save_cur_using_data_addr(get_cur_using_data_addr())) == EF_NO_ERR) {
				EF_INFO("Warning: ENV end address has error. Set it to default.\n");
				result = ef_env_set_default();
			}
		} else {
			/* set ENV detail part end address */
			set_env_detail_end_addr(env_end_addr);

			env_cache_bak = env_cache + ENV_PARAM_PART_WORD_SIZE;
			/* read all ENV from flash */
			ef_port_read(get_env_detail_addr(), env_cache_bak, get_env_detail_size());
			/* read ENV CRC code from flash */
			ef_port_read(get_cur_using_data_addr() + ENV_PARAM_PART_INDEX_DATA_CRC * 4,
						 &env_cache[ENV_PARAM_PART_INDEX_DATA_CRC], 4);
			/* if ENV CRC32 check is fault, set default for it */
			if (!env_crc_is_ok()) {
				EF_INFO("Warning: ENV CRC check failed. Set it to default.\n");
				result = ef_env_set_default();
			}
		}

	}
	return result;
}
#else
EfErrCode ef_load_env(void)
{
	EfErrCode result = EF_NO_ERR;
	/* ENV area0 current using address default value */
	uint32_t area0_default_cur_using_addr = get_env_start_addr() + EF_ERASE_MIN_SIZE;
	/* ENV area1 current using address default value */
	uint32_t area1_default_cur_using_addr = area0_default_cur_using_addr + ENV_AREA_SIZE / 2;
	uint32_t area0_cur_using_addr, area1_cur_using_addr, area0_end_addr, area1_end_addr;
	uint32_t area0_crc, area1_crc, area0_saved_count, area1_saved_count;
	bool area0_is_valid = true, area1_is_valid = true;

	/* read ENV area0 and area1 current using data section address */
	ef_port_read(get_env_start_addr(), &area0_cur_using_addr, 4);
	ef_port_read(get_env_start_addr() + ENV_AREA_SIZE / 2, &area1_cur_using_addr, 4);
	/* if ENV is not initialize or flash has dirty data, set it isn't valid */
	if ((area0_cur_using_addr == 0xFFFFFFFF)
		|| (area0_cur_using_addr > get_env_start_addr() + ENV_AREA_SIZE / 2)
		|| (area0_cur_using_addr < get_env_start_addr() + EF_ERASE_MIN_SIZE))
		area0_is_valid = false;
	if ((area1_cur_using_addr == 0xFFFFFFFF)
		|| (area1_cur_using_addr > get_env_start_addr() + ENV_AREA_SIZE)
		|| (area1_cur_using_addr < get_env_start_addr() + ENV_AREA_SIZE / 2 + EF_ERASE_MIN_SIZE))
		area1_is_valid = false;
	/* check area0 end address when it is valid */
	if (area0_is_valid) {
		/* read ENV area end address from flash */
		ef_port_read(area0_cur_using_addr + ENV_PARAM_PART_INDEX_END_ADDR * 4, &area0_end_addr, 4);
		if ((area0_end_addr == 0xFFFFFFFF) || (area0_end_addr < area0_cur_using_addr)
			|| (area0_end_addr > area0_cur_using_addr + ENV_USER_SETTING_SIZE))
			area0_is_valid = false;
	}
	/* check area1 end address when it is valid */
	if (area1_is_valid) {
		/* read ENV area end address from flash */
		ef_port_read(area1_cur_using_addr + ENV_PARAM_PART_INDEX_END_ADDR * 4, &area1_end_addr, 4);
		if ((area1_end_addr == 0xFFFFFFFF) || (area1_end_addr < area1_cur_using_addr)
			|| (area1_end_addr > area1_cur_using_addr + ENV_USER_SETTING_SIZE))
			area1_is_valid = false;
	}
	/* check area0 CRC when it is valid */
	if (area0_is_valid) {
		/* read ENV area0 crc32 code from flash */
		ef_port_read(area0_cur_using_addr + ENV_PARAM_PART_INDEX_DATA_CRC * 4, &area0_crc, 4);
		/* read ENV from ENV area0 */
		ef_port_read(area0_cur_using_addr, env_cache, area0_end_addr - area0_cur_using_addr);
		/* current using data section address is area0 current using data section address */
		set_cur_using_data_addr(area0_cur_using_addr);
		if (!env_crc_is_ok())
			area0_is_valid = false;
	}
	/* check area1 CRC when it is valid */
	if (area1_is_valid) {
		/* read ENV area1 crc32 code from flash */
		ef_port_read(area1_cur_using_addr + ENV_PARAM_PART_INDEX_DATA_CRC * 4, &area1_crc, 4);
		/* read ENV from ENV area1 */
		ef_port_read(area1_cur_using_addr, env_cache, area1_end_addr - area1_cur_using_addr);
		/* current using data section address is area1 current using data section address */
		set_cur_using_data_addr(area1_cur_using_addr);
		if (!env_crc_is_ok())
			area1_is_valid = false;
	}
	/* all ENV area CRC is OK then compare saved count */
	if (area0_is_valid && area1_is_valid) {
		/* read ENV area saved count from flash */
		ef_port_read(area0_cur_using_addr + ENV_PARAM_PART_INDEX_SAVED_COUNT * 4,
					 &area0_saved_count, 4);
		ef_port_read(area1_cur_using_addr + ENV_PARAM_PART_INDEX_SAVED_COUNT * 4,
					 &area1_saved_count, 4);
		/* the bigger saved count area is valid */
		if ((area0_saved_count > area1_saved_count) || ((area0_saved_count == 0) && (area1_saved_count == 0xFFFFFFFF)))
			area1_is_valid = false;
		else
			area0_is_valid = false;
	}
	if (area0_is_valid) {
		/* current using data section address is area0 current using data section address */
		set_cur_using_data_addr(area0_cur_using_addr);
		/* next save ENV area address is area1 current using address value */
		next_save_area_addr = area1_cur_using_addr;
		/* read all ENV from area0 */
		ef_port_read(area0_cur_using_addr, env_cache, area0_end_addr - area0_cur_using_addr);
	} else if (area1_is_valid) {
		/* already read data section and set_cur_using_data_addr above current code,
		 * so just set next save ENV area address is area0 current using address value */
		next_save_area_addr = area0_cur_using_addr;
	} else {
		/* current using data section address is area1 current using address default value */
		set_cur_using_data_addr(area1_default_cur_using_addr);
		/* next save ENV area address default is area0 current using address default value */
		next_save_area_addr = area0_default_cur_using_addr;
		/* save current using data section address to flash*/
		if (((result = save_cur_using_data_addr(area0_default_cur_using_addr)) == EF_NO_ERR)
			&& ((result = save_cur_using_data_addr(area1_default_cur_using_addr)) == EF_NO_ERR)) {
			/* set the ENV to default */
			result = ef_env_set_default();
		}
	}
	return result;
}
#endif

/**
 * Save ENV to flash.
 */
EfErrCode ef_save_env(void)
{
	EfErrCode result = EF_NO_ERR;
	uint32_t cur_using_addr_bak, move_offset_addr;
	size_t env_used_size = get_env_user_used_size();
	uint32_t data_sec_end_addr;

	/* ENV ram cache has not changed don't need to save */
	if (!env_cache_changed)
		return result;

#ifndef EF_ENV_USING_PFS_MODE
	data_sec_end_addr = get_env_start_addr() + ENV_AREA_SIZE - 4;
	cur_using_addr_bak = get_cur_using_data_addr();
#else
	cur_using_addr_bak = next_save_area_addr;
	/* replace next_save_area_addr with cur_using_data_addr */
	next_save_area_addr = get_cur_using_data_addr();
	set_cur_using_data_addr(cur_using_addr_bak);
	/* change the ENV detail end address to next save area address */
	set_env_detail_end_addr(get_cur_using_data_addr() + env_used_size);
	/* area0 or area1 */
	if (get_cur_using_data_addr() < get_env_start_addr() + ENV_AREA_SIZE / 2)
		data_sec_end_addr = get_env_start_addr() + ENV_AREA_SIZE / 2 - 4;
	else
		data_sec_end_addr = get_env_start_addr() + ENV_AREA_SIZE - 4;
	/* ENV area saved count +1 */
	env_cache[ENV_PARAM_PART_INDEX_SAVED_COUNT]++;
#endif

	/* wear leveling process, automatic move ENV to next available position */
	while (get_cur_using_data_addr() + env_used_size < data_sec_end_addr) {
		/* calculate and cache CRC32 code */
		env_cache[ENV_PARAM_PART_INDEX_DATA_CRC] = calc_env_crc();
		/* erase ENV */
		result = ef_port_erase(get_cur_using_data_addr(), env_used_size);
		switch (result) {
		case EF_NO_ERR: {
			EF_INFO("Erased ENV OK.\n");
			break;
		}
		case EF_ERASE_ERR: {
			EF_INFO("Warning: Erased ENV fault! Start address is 0x%08X, size is %ld.\n",
					get_cur_using_data_addr(), env_used_size);
			EF_INFO("Moving ENV to next available position.\n");
			/* Calculate move offset address.
			 * Current strategy is optimistic. It will offset the flash erasure minimum size.
			 */
			move_offset_addr = EF_ERASE_MIN_SIZE;
			/* calculate and set next available data section address */
			set_cur_using_data_addr(get_cur_using_data_addr() + move_offset_addr);
			/* calculate and set next available ENV detail part end address */
			set_env_detail_end_addr(get_env_detail_end_addr() + move_offset_addr);
			continue;
		}
		}
		/* write ENV to flash */
		result = ef_port_write(get_cur_using_data_addr(), env_cache, env_used_size);
		switch (result) {
		case EF_NO_ERR: {
			EF_INFO("Saved ENV OK.\n");
			break;
		}
		case EF_WRITE_ERR: {
			EF_INFO("Warning: Saved ENV fault! Start address is 0x%08X, size is %ld.\n",
					get_cur_using_data_addr(), env_used_size);
			EF_INFO("Moving ENV to next available position.\n");
			/* Calculate move offset address.
			 * Current strategy is optimistic. It will offset the flash erasure minimum size.
			 */
			move_offset_addr = EF_ERASE_MIN_SIZE;
			/* calculate and set next available data section address */
			set_cur_using_data_addr(get_cur_using_data_addr() + move_offset_addr);
			/* calculate and set next available ENV detail part end address */
			set_env_detail_end_addr(get_env_detail_end_addr() + move_offset_addr);
			continue;
		}
		}
		/* save ENV success */
		if (result == EF_NO_ERR)
			break;
	}

	if (get_cur_using_data_addr() + env_used_size < data_sec_end_addr) {
		/* current using data section address has changed, save it */
		if (get_cur_using_data_addr() != cur_using_addr_bak)
			result = save_cur_using_data_addr(get_cur_using_data_addr());
	} else {
		result = EF_ENV_FULL;
		EF_INFO("Error: The flash has no available space to save ENV.\n");
	}

	env_cache_changed = false;

	return result;
}

/**
 * Calculate the cached ENV CRC32 value.
 *
 * @return CRC32 value
 */
static uint32_t calc_env_crc(void)
{
	uint32_t crc32 = 0;

	/* Calculate the ENV end address and all ENV data CRC32.
	 * The 4 is ENV end address bytes size. */
	crc32 = ef_calc_crc32(crc32, &env_cache[ENV_PARAM_PART_INDEX_END_ADDR], 4);
	crc32 = ef_calc_crc32(crc32, &env_cache[ENV_PARAM_PART_WORD_SIZE], get_env_detail_size());
	EF_DEBUG("Calculate ENV CRC32 number is 0x%08X.\n", crc32);

	return crc32;
}

/**
 * Check the ENV CRC32
 *
 * @return true is ok
 */
static bool env_crc_is_ok(void)
{
	if (calc_env_crc() == env_cache[ENV_PARAM_PART_INDEX_DATA_CRC]) {
		EF_DEBUG("Verify ENV CRC32 result is OK.\n");
		return true;
	} else
		return false;
}

/**
 * Save current using data section address to flash.
 *
 * @param cur_data_addr current using data section address
 *
 * @return result
 */
#ifndef EF_ENV_USING_PFS_MODE
static EfErrCode save_cur_using_data_addr(uint32_t cur_data_addr)
{
	EfErrCode result = EF_NO_ERR;

	/* erase ENV system section */
	result = ef_port_erase(get_env_start_addr(), 4);
	if (result == EF_NO_ERR) {
		/* write current using data section address to flash */
		result = ef_port_write(get_env_start_addr(), &cur_data_addr, 4);
		if (result == EF_WRITE_ERR) {
			EF_INFO("Error: Write system section fault! Start address is 0x%08X, size is %ld.\n",
					get_env_start_addr(), 4);
			EF_INFO("Note: The ENV can not be used.\n");
		}
	} else {
		EF_INFO("Error: Erased system section fault! Start address is 0x%08X, size is %ld.\n",
				get_env_start_addr(), 4);
		EF_INFO("Note: The ENV can not be used\n");
	}
	return result;
}
#else
static EfErrCode save_cur_using_data_addr(uint32_t cur_data_addr)
{
	EfErrCode result = EF_NO_ERR;
	uint32_t cur_system_sec_addr;

	if (cur_data_addr < get_env_start_addr() + ENV_AREA_SIZE / 2) {
		/* current using system section is in ENV area0 */
		cur_system_sec_addr = get_env_start_addr();
	} else {
		/* current using system section is in ENV area1 */
		cur_system_sec_addr = get_env_start_addr() + ENV_AREA_SIZE / 2;
	}
	/* erase ENV system section */
	result = ef_port_erase(cur_system_sec_addr, 4);
	if (result == EF_NO_ERR) {
		/* write area0 and area1 current using data section address to flash */
		result = ef_port_write(cur_system_sec_addr, &cur_data_addr, 4);
		if (result == EF_WRITE_ERR) {
			EF_INFO("Error: Write system section fault! Start address is 0x%08X, size is %ld.\n",
					cur_system_sec_addr, 4);
			EF_INFO("Note: The ENV can not be used.\n");
		}
	} else {
		EF_INFO("Error: Erased system section fault! Start address is 0x%08X, size is %ld.\n",
				cur_system_sec_addr, 4);
		EF_INFO("Note: The ENV can not be used\n");
	}
	return result;
}
#endif

/**
 * Set and save an ENV. If set ENV is success then will save it.
 *
 * @param key ENV name
 * @param value ENV value
 *
 * @return result
 */
EfErrCode ef_set_and_save_env(const char *key, const char *value)
{
	EfErrCode result = EF_NO_ERR;

	result = ef_set_env(key, value);

	if (result == EF_NO_ERR)
		result = ef_save_env();

	return result;
}

#endif /* EF_ENV_USING_WL_MODE */

#endif /* EF_USING_ENV */
